package controller

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"kim/service"
	"strconv"

	"gopkg.in/fatih/set.v0"
	"net/http"
	"sync"
)

type ChatController struct {
}

type Node struct {
	UserId int64
	Conn   *websocket.Conn
	//并行转串行,
	DataQueue chan []byte
	GroupSets set.Interface
}

type Message struct {
	Id      int64  `json:"id,omitempty" form:"id"`           //消息ID
	Userid  int64  `json:"userid,omitempty" form:"userid"`   //谁发的
	Cmd     int    `json:"cmd,omitempty" form:"cmd"`         //群聊还是私聊
	Dstid   int64  `json:"dstid,omitempty" form:"dstid"`     //对端用户ID/群ID
	Media   int    `json:"media,omitempty" form:"media"`     //消息按照什么样式展示
	Content string `json:"content,omitempty" form:"content"` //消息的内容
	Pic     string `json:"pic,omitempty" form:"pic"`         //预览图片
	Url     string `json:"url,omitempty" form:"url"`         //服务的URL
	Memo    string `json:"memo,omitempty" form:"memo"`       //简单描述
	Amount  int    `json:"amount,omitempty" form:"amount"`   //其他和数字相关的

}

//定义命令行格式
const (
	CmdSingleMsg = 10
	CmdRoomMsg   = 11
	CmdHeart     = 0
)

//userid和Node映射关系表
var clientMap map[int64]*Node = make(map[int64]*Node, 0)

//读写锁
var rwlocker sync.RWMutex

//TODO  信道读写逻辑

// ws协议 升级  拿到句柄

func (ChatController) UpGradeHandler(ctx *gin.Context) {

	fmt.Println("执行ws协议升级")

	//TODO 鉴权
	id := ctx.Query("id")
	userId, _ := strconv.ParseInt(id, 10, 64)

	var upGrader = websocket.Upgrader{
		CheckOrigin: func(r *http.Request) bool { // CheckOrigin解决跨域问题
			return true
		},
	}

	conn, err := upGrader.Upgrade(ctx.Writer, ctx.Request, nil)
	if err != nil {
		fmt.Println("ws协议升级异常")
		fmt.Println(err)
	}

	node := &Node{
		UserId:    userId,
		Conn:      conn,
		DataQueue: make(chan []byte, 50),
		GroupSets: set.New(set.ThreadSafe),
	}

	// 完成上线工作
	rwlocker.Lock()
	clientMap[userId] = node
	rwlocker.Unlock()

	// 上线 获取 离线消息
	records := service.GetOffLineRecordsByUserId(userId)

	recordJson, _ := json.Marshal(records)

	conn.WriteMessage(websocket.TextMessage, recordJson)

	groupIds := service.GetGroupsByUserId(userId)
	for _, groupId := range groupIds {
		node.GroupSets.Add(groupId) // 将该用户所在的 群聊id 都放进集合
	}

	//从客户端接收数据
	go node.Read()

	// 从客户端收到数据
	go node.Write()

	//ctx.JSON(200, gin.H{   你搞这种返回就破坏了连接
	//	"message": "上线",
	//})

	//TODO  没有处理离线逻辑啊

}

//处理发送逻辑
func (this *Node) Write() {

	for {
		select {
		case data := <-this.DataQueue:
			err := this.Conn.WriteMessage(websocket.TextMessage, data)
			if err != nil {
				fmt.Println(err)

			}
		}
	}

}

func (this *Node) Read() {

	defer func() {
		rwlocker.Lock()
		delete(clientMap, this.UserId)
		rwlocker.Unlock()

		_ = this.Conn.Close()
		fmt.Printf("用户%d 完成下线", this.UserId)

	}()

	for {
		_, data, err := this.Conn.ReadMessage()

		if err != nil {
			fmt.Println(err)
		}

		// 转发这个  数据
		msg := Message{}
		err = json.Unmarshal(data, &msg)

		if err != nil {
			fmt.Println(err)
			break // 很重要  出发掉线逻辑
		}

		// 先处理私聊信息
		if msg.Cmd == 1 {
			flag := SendMsgByUserId(msg.Dstid, data)

			strId := strconv.Itoa(int(msg.Dstid))

			replyMsg := &Message{}

			if flag {
				replyMsg = &Message{
					Dstid:   msg.Dstid,
					Content: "已经将消息发送给" + strId + ", 对方在线",
				}
			} else {
				replyMsg = &Message{
					Dstid:   msg.Dstid,
					Content: "已经将消息发送给" + strId + ", 对方不在线",
				}

				service.InsertOffLineMsg(data)

			}

			// Go  可以没有get Bytes  这么容易的转化   但是你可以格式化成json后进行传输
			replyJson, _ := json.Marshal(replyMsg)
			this.Conn.WriteMessage(websocket.TextMessage, replyJson)
		} else if msg.Cmd == 2 {
			// 处理群聊逻辑
			cnt := 0
			rwlocker.RLock()
			for _, v := range clientMap {
				if v.GroupSets.Has(msg.Dstid) {
					v.DataQueue <- data
					cnt++
				}
			}
			rwlocker.RUnlock()
			strCnt := strconv.Itoa(cnt)
			replyMsg := &Message{
				Content: "累计发送给了" + strCnt + "个客户端",
			}

			replyJson, _ := json.Marshal(replyMsg)

			this.Conn.WriteMessage(websocket.TextMessage, replyJson)

		}

		service.InsertMsg(data, true)

	}

}

//SendMsgByUserId 向单个用户发送  信息
func SendMsgByUserId(userId int64, data []byte) bool {
	rwlocker.RLock()
	node, flag := clientMap[userId]
	rwlocker.RUnlock()
	if flag {
		node.DataQueue <- data

	}

	// 离线消息处理逻辑

	return flag

}
